package model;

import static util.Constants.BLANK_SYMBOL;
import static util.Constants.RACK_SIZE;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import javax.swing.event.EventListenerList;

import model.events.RackEvent;
import model.iview.IRack;
import model.iview.ITile;
import model.listeners.RackListener;

import exceptions.BadMoveException;
import exceptions.EmptyBagException;

/**
 * Cette classe s'occupe des lettres que possède un joueur à chaque instant.
 * 
 * @author Lucile Gramusset et Guillaume Poussel
 * 
 */
public class Rack implements IRack {

    /**
     * La liste des tuiles du joueur
     */
    private ArrayList<Tile> tiles;

    /**
     * <i>Listeners</i> des changements du rack
     */
    private EventListenerList rackListeners = new EventListenerList();

    /**
     * Construit un rack vide
     */
    public Rack() {
        tiles = new ArrayList<Tile>();
    }

    /**
     * Détermine si le rack contient une lettre précide
     * 
     * @param letter
     *            La lettre à vérifier
     * @return <code>true</code> si le rack contient la lettre,
     *         <code>false</code> sinon.
     */
    public boolean contains(Letter letter) {
        for (ITile t : tiles) {
            if (t.getLetter() == letter)
                return true;
        }
        return false;
    }

    /**
     * Détermine si le rack contient une liste de lettres. Cette méthode tient
     * compte du fait de lettres en double.
     * 
     * @param letters
     *            La liste de lettres
     * @return <code>true</code> si toutes les lettres sont contenues sur le
     *         rack, <code>false</code> sinon.
     */
    @SuppressWarnings("unchecked")
    public boolean containsAll(ArrayList<Letter> letters) {
        ArrayList<Tile> copie = (ArrayList<Tile>) tiles.clone();
        for (Letter l : letters) {
            boolean trouve = false;
            Iterator<Tile> it = copie.iterator();
            while (it.hasNext() && !trouve) {
                Letter currLetter = it.next().getLetter();
                if (currLetter.equals(l)) {
                    trouve = true;
                    it.remove();
                }
            }
            if (!trouve) {
                Iterator<Tile> iterator = copie.iterator();
                while (iterator.hasNext() && !trouve) {
                    Letter currLetter = iterator.next().getLetter();
                    if (currLetter.equals(Letter.BLANK)) {
                        trouve = true;
                        iterator.remove();
                    }
                }
                if (!trouve)
                    return false;
            }
        }
        return true;
    }

    /**
     * Supprime toutes les tuiles passées en paramètre
     * 
     * @param tiles
     *            Liste des tuiles à supprimer
     */
    public void removeAll(Collection<Tile> tiles) {
        for (ITile t : tiles) {
            this.tiles.remove(t);
        }
    }

    /**
     * Retire certaines tuiles en fonction des lettres passées en paramètre
     * 
     * @param letters
     *            La liste des lettres
     * @return La liste des tuiles correspondant aux lettres passées en
     *         paramètre.
     */
    public ArrayList<Tile> takeAll(ArrayList<Letter> letters) {
        ArrayList<Tile> tiles = new ArrayList<Tile>();
        for (Letter l : letters) {
            boolean trouve = false;
            Iterator<Tile> it = this.tiles.iterator();
            while (it.hasNext() && !trouve) {
                Tile currTile = it.next();
                if (currTile.getLetter().equals(l)) {
                    trouve = true;
                    it.remove();
                    tiles.add(currTile);

                }
            }
            if (!trouve) {
                Iterator<Tile> iterator = this.tiles.iterator();
                while (iterator.hasNext() && !trouve) {
                    Letter currLetter = iterator.next().getLetter();
                    if (currLetter.equals(Letter.BLANK)) {
                        trouve = true;
                        iterator.remove();
                        Tile tileBlank = new Tile(Letter.BLANK, 0);
                        tileBlank.setLetter(l);
                        tiles.add(tileBlank);
                    }
                }
                if (!trouve)
                    throw new BadMoveException();
            }
        }
        fireRackListeners();
        return tiles;
    }

    /**
     * Ajoute toutes les tuiles au rack
     * 
     * @param tiles
     *            La liste des tuiles à ajouter au rack
     */
    public void refillAll(ArrayList<Tile> tiles) {
        this.tiles.addAll(tiles);
        fireRackListeners();
    }

    /**
     * Remplit le rack à partir du sac passé en paramètre. Si le sac n'est pas
     * suffisamment rempli, le rack n'aura pas sa taille maximale, mais aucune
     * erreur n'est levée.
     * 
     * @param bag
     *            Le sac dont on extrait les lettres
     */
    public void fill(Bag bag) {
        try {
            while (tiles.size() < RACK_SIZE) {
                tiles.add(bag.take());
                fireRackListeners();
            }
        } catch (EmptyBagException e) {
            // Le sac est vide, tant pis ...
        }
    }

    /**
     * @return <code>true</code> si le sac est vide, <code>false</code> sinon.
     */
    public boolean isEmpty() {
        return tiles.isEmpty();
    }

    /**
     * Prend une lettre depuis le rack et retourne la tuile associée
     * 
     * @param letter
     *            La lettre à retirer
     * @return La tuile correspond à la lettre passée en paramètre,
     *         <code>null</code> si on ne possède pas cette lettre.
     */
    public ITile take(Letter letter) {
        Iterator<Tile> it = tiles.iterator();
        while (it.hasNext()) {
            ITile tile = it.next();
            if (tile.getLetter() == letter) {
                it.remove();
                fireRackListeners();
                return tile;
            }
        }

        return null;
    }

    @Override
    public String toString() {
        String strReturn = "";
        for (int i = 0; i < tiles.size(); i++)
            strReturn += tiles.get(i).getLetter() == Letter.BLANK ? BLANK_SYMBOL
                    : tiles.get(i).getLetter();
        return strReturn;
    }

    /**
     * Retourne le total du score de chacune des lettres de ce rack
     * @return Le total du score des lettres
     */
    public int getTotalScore() {
        int score = 0;
        for (ITile t : tiles) {
            score += t.getScore();
        }
        return score;
    }

    /**
     * @return Les tuiles contenues sur ce rack
     */
    public ArrayList<Tile> getTiles() {
        return this.tiles;
    }
    
    /**
     * @return Les tuiles contenues sur ce rack
     */
    public ArrayList<ITile> getITiles() {
        ArrayList<ITile> itiles = new ArrayList<ITile>();
        for (Tile t : getTiles()) {
            itiles.add(t);
        }
        return itiles;
    }

    /**
     * Avertit les <i>listeners</i> du changement du rack.
     */
    public void fireRackListeners() {
        RackEvent pe = new RackEvent(this, this);
        for (RackListener pl : rackListeners.getListeners(RackListener.class)) {
            pl.rackChanged(pe);
        }
    }

    /**
     * Ajoute un <i>listener</i> à ce rack.
     * @param rackListener Le listener à ajouter
     */
    public void addRackListener(RackListener rackListener) {
        this.rackListeners.add(RackListener.class, rackListener);

    }

    /**
     * Supprime un <i>listener</i> de ce rack
     * @param rackListener Le listener à supprimer
     */
    public void removeRackListener(RackListener rackListener) {
        this.rackListeners.remove(RackListener.class, rackListener);
    }

    /**
     * Retourne la liste des listeners du rack
     * @return La liste des listeners du rack
     */
    public RackListener[] getRackListeners() {
        return this.rackListeners.getListeners(RackListener.class);
    }
}
